package socket

import (
	"context"
	"errors"

	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/utils/def"
	"gddgame.cc/galaxy/utils/serialize"
	"gddgame.cc/galaxy/utils/serialize/msgpack"
	"github.com/antlabs/timer"
)

type OnHandler func(response Response, err error)

type ListenEventHandler func(request Request)
type ListenStreamHandler func(request StreamRequest) bool

var (
	s = msgpack.Msgpack(false)
)

const (
	RequestOnceType MessageType = iota
	RequestStream
	ResponseOnceType
	ResponseStream
	FinishStream
	ResponseErrorType
)

type Request interface {
	def.Context
	Data(v interface{}) error
	From() string
	Region() string
	Response(data interface{}, serializer ...def.AutoSerializer)
	Serialize() def.OptimizeSerializer
	Async()
}

type StreamRequest interface {
	ID() string
	Data(v interface{}) error
	Region() string
	From() string
	Push(data interface{}, serializer ...def.AutoSerializer)
	Finish()
}

type Response interface {
	ID() string
	Data(v interface{}) error
	Release()
}

type Message interface {
	Strategy(strategy asteroid.Strategy) Message
	Hash() Message
	Handler(fn OnHandler) Message
	AutoRelease(serializer ...def.AutoSerializer) Message
	Do(event string, data interface{}) error
	Stream(event string, data interface{}) (chan Response, context.Context, Stream, error)
}

type Stream interface {
	Finish()
}

type reqResp struct {
	def.Logger
	def.Store

	socket *socket

	id     string
	region string
	from   string
	node   string

	handler  OnHandler
	strategy asteroid.Strategy
	hash     bool
	manual   bool

	slot        serialize.Slot
	releaseList []def.AutoSerializer

	time       timer.TimeNoder
	receive    chan Response
	cancelFunc context.CancelFunc
}

func (message *reqResp) ID() string {
	return message.id
}

func (message *reqResp) Data(v interface{}) error {
	if message.slot == nil {
		return errors.New("only response can data")
	}
	return message.slot.Bind(s).Set(v)
}

func (message *reqResp) Serialize() def.OptimizeSerializer {
	if message.slot != nil {
		message.slot.Alloc()
	}

	return message.slot
}

func (message *reqResp) From() string {
	return message.from
}

func (message *reqResp) Region() string {
	return message.region
}

func (message *reqResp) Async() {
	message.manual = true
}

func (message *reqResp) Response(data interface{}, serializer ...def.AutoSerializer) {
	message.manual = true
	if err, ok := (data).(error); ok {
		message.socket.responseError(message, message.id, err)
		message.release()
		return
	}
	slot := serialize.GetSlot()

	if err := slot.Bind(s).Set(data); err != nil {
		message.socket.responseError(message, message.id, err)
		message.release()
		slot.Release()
		return
	}
	message.socket.responseDo(message, message.id, slot, serializer)
	message.release()
	slot.Release()
}

func (message *reqResp) Push(data interface{}, serializer ...def.AutoSerializer) {
	slot := serialize.GetSlot()
	if err := slot.Bind(s).Set(data); err != nil {
		message.socket.responseError(message, message.id, err)
		message.release()
		slot.Release()
		return
	}
	message.socket.responseStream(message, message.id, slot, serializer)
	slot.Release()
}

func (message *reqResp) Strategy(strategy asteroid.Strategy) Message {
	message.strategy = strategy
	return message
}

func (message *reqResp) Hash() Message {
	message.hash = true
	return message
}

func (message *reqResp) Handler(fn OnHandler) Message {
	message.handler = fn
	return message
}

func (message *reqResp) Do(event string, data interface{}) error {
	slot := serialize.GetSlot()
	if err := slot.Bind(s).Set(data); err != nil {
		message.release()
		slot.Release()
		return err
	}
	message.id = message.socket.message(message, RequestOnceType, event, slot, message.releaseList)
	message.releaseList = message.releaseList[0:0]
	if message.handler != nil {
		message.time = message.socket.AfterFunc(message.socket.timeout, func() {
			message.time = nil
			message.handler(nil, errors.New("Timeout"))
			message.socket.reqIdMap.Delete(message.id)
			message.release()
		})
	} else {
		message.release()
	}
	slot.Release()
	return nil
}

func (message *reqResp) Stream(event string, data interface{}) (chan Response, context.Context, Stream, error) {
	slot := serialize.GetSlot()
	if err := slot.Bind(s).Set(data); err != nil {
		message.release()
		slot.Release()
		return nil, nil, nil, err
	}
	ctx, cancelFunc := context.WithCancel(message.socket.context())
	message.cancelFunc = cancelFunc
	message.receive = make(chan Response)
	message.id = message.socket.message(message, RequestStream, event, slot, message.releaseList)
	message.releaseList = message.releaseList[0:0]
	slot.Release()
	return message.receive, ctx, message, nil
}

func (message *reqResp) AutoRelease(serializer ...def.AutoSerializer) Message {
	message.releaseList = append(message.releaseList, serializer...)
	return message
}

func (message *reqResp) Finish() {
	message.socket.finishStream(message, message.id)
}

func (message *reqResp) Release() {
	message.release()
}

func (message *reqResp) release() {
	if message.cancelFunc != nil {
		message.cancelFunc()
	}
	message.id = ""
	message.region = ""
	message.from = ""
	message.node = ""
	message.hash = false
	message.cancelFunc = nil
	message.strategy = nil
	message.receive = nil
	message.handler = nil
	message.manual = false
	if message.slot != nil {
		message.slot.Release()
		message.slot = nil
	}
	//message.socket.release(message)
	if message.time != nil {
		message.time.Stop()
		message.time = nil
	}

	message.socket.release(message)
}
